#!/usr/bin/env bash

# Copyright 2020 the Exposure Notifications Server authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eEuo pipefail

PROGNAME="$(basename $0)"
ROOT="$(cd "$(dirname "$0")/.." &>/dev/null; pwd -P)"

DOCKER_CMD="$(command -v docker || true)"
DB_CONTAINER_IMAGE="registry.hub.docker.com/library/postgres:13-alpine"

# Database parameters
DB_HOST="${DB_HOST:-"127.0.0.1"}"
DB_NAME="${DB_NAME:-"en-server-db"}"
DB_PASSWORD="${DB_PASSWORD:-"6f50ecd73c7668c2"}"
DB_PORT="${DB_PORT:-"5432"}"
DB_USER="${DB_USER:-"en-server"}"
DB_SSLMODE="${DB_SSLMODE:-"require"}"
DB_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=${DB_SSLMODE}"

MD5CMD="$(command -v md5 || command -v md5sum)"
DB_SHA="$(echo -n "${DB_NAME}|${DB_PORT}" | ${MD5CMD} | cut -d' ' -f1)"
CERTS_DIR="${ROOT}/local/db-tls/${DB_SHA}"

# error prints a message on stderr and exits immediately.
function error() {
  echo "✋ ERROR: $1" >&2
  exit 1
}

function init() {
  echo "export DB_HOST=\"${DB_HOST}\""
  echo "export DB_NAME=\"${DB_NAME}\""
  echo "export DB_USER=\"${DB_USER}\""
  echo "export DB_PASSWORD=\"${DB_PASSWORD}\""
  echo "export DB_PORT=\"${DB_PORT}\""
  echo "export DB_SSLMODE=\"${DB_SSLMODE}\""
  echo "export DB_URL=\"${DB_URL}\""
}

# gencerts creates the postgres SSL certificates if they do not already exist.
function gencerts() {
  if [ -d "${CERTS_DIR}" ]; then
    return
  fi

  local PASSWORD="$(openssl rand -hex 8)"
  rm -rf "${CERTS_DIR}" && mkdir -p "${CERTS_DIR}"

  openssl req -new -text -passout "pass:${PASSWORD}" -subj "/CN=localhost" -out "${CERTS_DIR}/server.csr" -keyout "${CERTS_DIR}/ca.pem" &>/dev/null
  openssl rsa -in "${CERTS_DIR}/ca.pem" -passin "pass:${PASSWORD}" -out "${CERTS_DIR}/server.key" &>/dev/null
  openssl req -x509 -in "${CERTS_DIR}/server.csr" -text -key "${CERTS_DIR}/server.key" -out "${CERTS_DIR}/server.crt" &>/dev/null

  chmod 0600 "${CERTS_DIR}/server.key"
  chmod 0644 "${CERTS_DIR}/server.crt"
  if [ $(uname -s) == "Linux" ]; then
    sudo chown 70 "${CERTS_DIR}/server.key" "${CERTS_DIR}/server.crt"
  fi
}

# docker executes the given command using the docker executable. If "docker" is
# not installed and in $PATH, it prints an error and exits.
function docker() {
  if [ -z "${DOCKER_CMD:-}" ]; then
    error "docker is not installed or is not in \$PATH"
  fi

  ${DOCKER_CMD} "$@"
}

# running determines if the database is running.
function running() {
  local out="$(docker inspect -f "{{.State.Running}}" "${DB_NAME}" 2>&1)"
  if [[ "${out}" == "true" ]]; then
    return 0
  else
    return 1
  fi
}

# stop terminates the database.
function stop() {
  docker rm --force "${DB_NAME}" > /dev/null
  echo "Database stopped (OK)"
}

# start creates and provisions a new database.
function start() {
  if running; then
    error "database is already running!"
  fi

  gencerts

  docker pull --quiet "${DB_CONTAINER_IMAGE}" > /dev/null
  docker run \
    --name "${DB_NAME}" \
    --env "LANG=C" \
    --env "POSTGRES_DB=${DB_NAME}" \
    --env "POSTGRES_USER=${DB_USER}" \
    --env "POSTGRES_PASSWORD=${DB_PASSWORD}" \
    --detach \
    --publish "${DB_PORT}:5432" \
    --volume "${CERTS_DIR}/server.crt:/var/lib/postgresql/server.crt:ro" \
    --volume "${CERTS_DIR}/server.key:/var/lib/postgresql/server.key:ro" \
    "${DB_CONTAINER_IMAGE}" \
      -c "shared_buffers=256MB" \
      -c "max_connections=200" \
      -c "ssl=on" \
      -c "ssl_cert_file=/var/lib/postgresql/server.crt" \
      -c "ssl_key_file=/var/lib/postgresql/server.key" \
      > /dev/null

  echo "Database started (OK)"
}

# psql creates a psql session.
function psql() {
  docker exec \
    --interactive \
    --tty \
    --env "PGPASSWORD=${DB_PASSWORD}" \
    "${DB_NAME}" \
    /usr/local/bin/psql \
      --dbname "${DB_NAME}" \
      --username "${DB_USER}" \
      --port "5432"
}

# migrate runs the migrations against the database.
function migrate() {
  go run ./cmd/migrate
}

# seed inserts test data.
function seed() {
  go run ./tools/seed
}

# dburl returns the full connection url.
function dburl() {
  echo "${DB_URL}"
}

# help prints help.
function help() {
  echo 1>&2 "Usage: ${PROGNAME} <command>"
  echo 1>&2 ""
  echo 1>&2 "Commands:"
  echo 1>&2 "  init         initialization for sourcing"
  echo 1>&2 "  dbstart      start a dev server"
  echo 1>&2 "  dbstop       stop the dev server"
  echo 1>&2 "  dbmigrate    run migrations"
  echo 1>&2 "  dbseed       seed dev data"
  echo 1>&2 "  dburl        print url"
  echo 1>&2 "  dbshell      attach a psql session"
}

SUBCOMMAND="${1:-}"
case "${SUBCOMMAND}" in
  "" | "help" | "-h" | "--help" )
    help
    ;;

  "dbstart" )
    shift
    start "$@"
    ;;

  "dbstop" )
    shift
    stop "$@"
    ;;

  "init" )
    shift
    init "$@"
    ;;

  "dbshell" )
    shift
    psql "$@"
    ;;

  "dbmigrate" )
    shift
    migrate "$@"
    ;;

  "dbseed" )
    shift
    seed "$@"
    ;;

  "dburl" )
    shift
    dburl "$@"
    ;;

  *)
    help
    exit 1
    ;;
esac
